package cloudfile

import (
	"context"
	"errors"
	"fmt"
	"github.com/suyuan32/simple-admin-file/ent"
	"github.com/suyuan32/simple-admin-file/pkg/enum/cloudprovider"
	"github.com/suyuan32/simple-admin-file/pkg/svc"
	"github.com/suyuan32/simple-admin-file/pkg/upload"
	"github.com/suyuan32/simple-admin-file/pkg/utils/cloud"
	"github.com/suyuan32/simple-admin-file/pkg/utils/filex"
	"mime/multipart"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/duke-git/lancet/v2/datetime"
	"github.com/suyuan32/simple-admin-common/utils/uuidx"
)

// S3Adapter 将 *s3.S3 适配为 upload.StorageClient
type S3Adapter struct {
	Client *s3.S3
}

func (a *S3Adapter) PutObjectWithContext(ctx context.Context, input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
	return a.Client.PutObjectWithContext(ctx, input)
}

// CloudUploader 是 Uploader 接口的具体实现
type CloudUploader struct {
	config   *upload.Config
	storage  map[string]upload.StorageClient
	provider map[string]upload.ProviderData
}

// isValidProvider 检查 provider 是否为合法值
func isValidProvider(provider string) bool {
	return provider == cloudprovider.Tencent || provider == cloudprovider.Aliyun
}

// NewCloudUploaderFromDB 从数据库构建 CloudUploader 实例
func NewCloudUploaderFromDB(db *ent.Client) *CloudUploader {
	cloudService := cloud.NewCloudServiceGroup(db)

	// 构建 storage map: string -> StorageClient（即 *S3Adapter）
	storageClients := make(map[string]upload.StorageClient)
	for name, s3Client := range cloudService.CloudStorage {
		storageClients[name] = &S3Adapter{Client: s3Client}
	}

	// 构建默认配置（可从 env 或 config 文件中读取）
	cfg := &upload.Config{
		MaxImageSize:        10 << 20,  // 默认 10MB
		MaxVideoSize:        100 << 20, // 默认 100MB
		MaxAudioSize:        30 << 20,  // 默认 30MB
		MaxOtherSize:        1 << 20,   // 默认 1MB
		DefaultProvider:     cloudService.DefaultProvider,
		PrivateStorePath:    "private",
		PublicStorePath:     "public",
		ServerURL:           "http://localhost:8080",
		DeleteFileWithCloud: true,
	}

	return &CloudUploader{
		config:   cfg,
		storage:  storageClients,
		provider: convertProviderData(cloudService.ProviderData),
	}
}

// convertProviderData 将 cloud.ProviderData 转换为 upload.ProviderData
func convertProviderData(data map[string]struct {
	Id       uint64
	Folder   string
	Bucket   string
	Endpoint string
	UseCdn   bool
	CdnUrl   string
}) map[string]upload.ProviderData {
	result := make(map[string]upload.ProviderData)
	for k, v := range data {
		result[k] = upload.ProviderData{
			Id:       v.Id,
			Folder:   v.Folder,
			Bucket:   v.Bucket,
			Endpoint: v.Endpoint,
			UseCdn:   v.UseCdn,
			CdnUrl:   v.CdnUrl,
		}
	}
	return result
}

// Upload 实现了 Uploader 接口的 Upload 方法
func (u *CloudUploader) Upload(file multipart.File, handler *multipart.FileHeader, provider string, svCtx *svc.ServiceContext) (*upload.UploadResult, error) {
	if !isValidProvider(provider) {
		return nil, fmt.Errorf("invalid provider: %s", provider)
	}

	// 校验后缀是否合法
	dotIndex := strings.LastIndex(handler.Filename, ".")
	if dotIndex == -1 {
		return nil, fmt.Errorf("invalid file name: %s", handler.Filename)
	}

	fileName, fileSuffix := handler.Filename[:dotIndex], handler.Filename[dotIndex+1:]
	fileUUID := uuidx.NewUUID()
	storeFileName := fileUUID.String() + "." + fileSuffix

	// 判断文件类型
	contentType := handler.Header.Get("Content-Type")
	parts := strings.Split(contentType, "/")
	fileType := "other"
	if len(parts) > 0 && parts[0] != "" {
		fileType = parts[0]
	}
	if fileType != "image" && fileType != "video" && fileType != "audio" {
		fileType = "other"
	}

	// ✅ 使用 filex.CheckOverSize 替代硬编码检查
	ctx := context.Background()
	if err := filex.CheckOverSize(ctx, svCtx, fileType, handler.Size); err != nil {
		return nil, err
	}

	// 获取相对路径
	relativeSrc := fmt.Sprintf("%s/%s/%s/%s",
		u.provider[provider].Folder,
		datetime.FormatTimeToStr(time.Now(), "yyyy-mm-dd"),
		fileType,
		storeFileName)

	// 上传到云存储
	url, err := u.uploadToProvider(file, relativeSrc, provider)
	if err != nil {
		return nil, fmt.Errorf("failed to upload file: %w", err)
	}

	if u.provider[provider].UseCdn {
		url = fmt.Sprintf("%s%s", ensureTrailingSlash(u.provider[provider].CdnUrl), relativeSrc)
	}

	return &upload.UploadResult{
		ID:          fileUUID.String(),
		Name:        fileName,
		URL:         url,
		RelativeSrc: relativeSrc,
		Size:        uint64(handler.Size),
		FileType:    filex.ConvertFileTypeToUint8(fileType),
		UserID:      "default_user_id", // 可替换为实际用户 ID
	}, nil
}

// 确保 URL 以 / 结尾
func ensureTrailingSlash(url string) string {
	if !strings.HasSuffix(url, "/") {
		return url + "/"
	}
	return url
}

// uploadToProvider 是上传到云存储的具体实现
func (u *CloudUploader) uploadToProvider(file multipart.File, fileName, provider string) (string, error) {
	if client, ok := u.storage[provider]; ok {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()

		_, err := client.PutObjectWithContext(ctx, &s3.PutObjectInput{
			Bucket: aws.String(u.provider[provider].Bucket),
			Key:    aws.String(fileName),
			Body:   file,
		})
		if err != nil {
			var aerr awserr.Error
			if errors.As(err, &aerr) {
				switch aerr.Code() {
				case request.CanceledErrorCode:
					return "", fmt.Errorf("upload canceled due to timeout: %w", err)
				case "EnvAccessKeyNotFound":
					return "", fmt.Errorf("AWS credentials not found: please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables")
				default:
					return "", fmt.Errorf("failed to upload object: %w", err)
				}
			}
			return "", fmt.Errorf("failed to upload object: %w", err)
		}

		// 动态生成上传 URL
		return fmt.Sprintf("https://%s.%s%s",
			u.provider[provider].Bucket,
			u.provider[provider].Endpoint, fileName), nil
	}

	return "", fmt.Errorf("provider not found: %s", provider)
}

// GetUploadURL 返回上传文件的 URL
func (u *CloudUploader) GetUploadURL(fileName, provider string) (string, error) {
	relativeSrc := fmt.Sprintf("%s/%s/%s/%s",
		u.provider[provider].Folder,
		datetime.FormatTimeToStr(time.Now(), "yyyy-mm-dd"),
		"image",
		fileName)

	url, err := u.uploadToProvider(nil, relativeSrc, provider)
	if err != nil {
		return "", err
	}

	if u.provider[provider].UseCdn {
		url = fmt.Sprintf("%s%s", u.provider[provider].CdnUrl, relativeSrc)
	}

	return url, nil
}
